/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import org.apache.log4j.Logger;

import be.destin.skos.search.LabelMatchType;
import be.destin.skos.search.SearchResult;
import be.destin.skos.search.SearchUtil;

/**
 * @author Christophe Dupriez
 * 
 */
@XmlType(propOrder = { "title","description","creator","contributor","namespace", "uri", "supraLanguage", "help","icon","display", "create", "internalNote", "usage" })
public class NotationScheme extends NoScheme implements SchemeUsageInterface {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/** log4j category */
	private static Logger log = Logger.getLogger(NotationScheme.class);

	private ConceptScheme primary;

	protected Map<String/* handle */, Concept> conceptsMap = new ConcurrentHashMap<String, Concept>();

	protected Map<String/* complete code */, String/* handle */> redirections = new ConcurrentHashMap<String, String>();

	/**
	 * 
	 */
	public NotationScheme() {
		super();
	}

	public NotationScheme(SkosManager managerInstance, String code) {
		super(managerInstance,code);
	}

	/**
	 * @param code
	 */
	public NotationScheme(SkosManager managerInstance, String code, ConceptScheme aPrimaryList) {
		super(managerInstance,code);
		setPrimary(aPrimaryList);
	}

	public String getAbout_About(String entryCode) {
		return this.getAbout()
				+ SchemeInterface.separator
				+ entryCode;
	}

	/*
	 * STUPID duplication made mandatory by XmlBinding...
	 */
	/**
	 * @return the displays
	 */
	@XmlElement
	public UrlList getDisplay() {
		return super.getDisplay();
	}

	@XmlElement
	public UrlList getCreate() {
		return creates;
	}

    /**
     * @return the internalNote
     */
    @XmlElement
    public String getInternalNote() {
        return super.getInternalNote();
    }

	/**
	 * @return the helps
	 */
	@XmlElement
	public UrlList getHelp() {
		return helps;
	}

	/**
	 * @return the icons
	 */
	@XmlElement
	public UrlList getIcon() {
		return super.getIcon();
	}

	/**
	 * @return the titles
	 */
	@XmlElement
	public TermList getTitle() {
		return super.getTitle();
	}

	/**
	 * @return the descriptions
	 */
	@XmlElement
	public TermList getDescription() {
		return super.getDescription();
	}

	/**
	 * @return the namespace
	 */
	@XmlElement(name="namespace")
	public String getNamespace() {
		return namespace;
	}

	/**
	 * @return the namespace
	 */
	@XmlElement(name="uri")
	public String getUri() {
		return uri;
	}

	/*
	 * END of DUPLICATION
	 */

	/**
	 * @return the complete prefixed code of an entry (after applying
	 *         redirections)
	 */
	public String getAbout_Concept(String handle) {
		Concept anEntry = findAbout_Concept(handle);
		return anEntry.getScheme_About();
	}

	/**
	 * @return the concepts
	 */
	@XmlTransient
	public synchronized LinkedList<Concept> getConcept() {
		return primary.getConcept();
	}
	/**
	 * @return the concepts
	 */
	@XmlTransient
	public synchronized LinkedList<Concept> getAllConcepts() {
		return primary.getAllConcepts();
	}
	//TODO: getHasTopConcept returning the NOTATIONS and not the abouts of the concepts...
	public synchronized LinkedList<ConceptIdentifier> getHasTopConcept() {
		return primary.getHasTopConcept();
	}
	
	/**
	 * @return the concepts for an handle (redirected or not). Null if none
	 */
	public synchronized LinkedList<Concept> getConcept(Collection<String> handles) {
		if (handles == null) return null;
		Concept anEntry;
		LinkedList<Concept> result = new LinkedList<Concept>();
		for (String anHandle : handles) {
			anEntry = this.getConcept(anHandle);
			if (anEntry != null) result.add(anEntry);
		}
		return result;
	}

	/**
	 * @return the concepts for an handle (redirected or not). Null if none
	 */
	public synchronized Concept getConcept(String aNotation) {
		if (aNotation == null) return null;
		aNotation = SkosUtil.normalizeAbout(aNotation);
		if (aNotation.length()==0) return null;
		synchronized(this) {
			if (this.conceptsMap.containsKey(aNotation))
				return this.conceptsMap.get(aNotation);
			if (this.redirections.containsKey(aNotation)) {
				String aKey = this.redirections.get(aNotation);
				return this.conceptsMap.get(aKey);
			}
		}
		if (primary.isDynamic())
			return primary.getNotationConcept(this.getAbout(),aNotation);
		else {
			log.debug(aNotation+" not found in notation "+getAbout());
			return null;
		}
	}

	/**
	 * @return the concepts
	 */
	@XmlTransient
	public synchronized Map<String, Concept> getConceptMap() {
		return this.conceptsMap;
	}

	/**
	 * @param language
	 * @return the best name for current language
	 */
	public String getDisplay(String language) {
		String display = super.getDisplay(language);
		if (display == null) display = primary.getDisplay(language);
		return display;
	}

	/**
	 * @param language
	 * @return the best name for current language
	 */
	public String getCreate(String language) {
		if (creates == null) return primary.getCreate(language);
		return creates.getLing(language);
	}

	/**
	 * @param language:
	 *            language of need help
	 * @return help URL in desired language
	 */
	public String getHelp(String language) {
		if (helps.getLing(language) == null)
			return primary.helps.getLing(language);
		return helps.getLing(language);
	}

	/**
	 * @param language
	 * @return the best name for current language
	 */
	public String getIcon(String language) {
		String icon = super.getIcon(language);
		if (icon == null) icon = primary.getIcon(language);
		return icon;
	}

	/**
	 * @return the code
	 */
	@XmlTransient
	public ConceptScheme getPrimary() {
		return primary;
	}

	/**
	 * @return the redirections
	 */
	@XmlTransient
	public Map<String, String> getAlias() {
		if (this.redirections == null) this.redirections = new ConcurrentHashMap<String,String>();
		return redirections;
	}

	/**
	 * @param language:
	 *            language of need help
	 * @return help URL in desired language
	 */
	public String getTitle(String language) {
		String aTitle = super.getTitle(language);
		if (aTitle == null)
			aTitle = primary.getTitle(language);
		return aTitle;
	}

	/**
	 * @return the knownIndexes
	 */
	public SchemeUsageList getUsage() {
		SchemeUsageList notationUsage = new SchemeUsageList();
		for (SchemeUsage anUsage : primary.getUsage()) {
			if (anUsage.getInScheme() == this) {
				notationUsage.put(anUsage);
			}
		}
		return notationUsage;
	}

	public SchemeUsage getUsage(String application, String role) {
		SchemeUsage usage = (this.getUsage().find(application, role));
		if (usage == null) {
			usage = new SchemeUsage( this, application, role);
			this.putSchemeUsage(usage);
		}
		return usage;
	}

	/**
	 * @param entry
	 *            to put amongst concepts: FORBIDDEN!
	 */
	public Concept putConcept(Concept entry) {
		String message = "IndexingAuthorityList cannot receive concepts without an index key";
		log.error(message);
		this.getErrors().add(message);
		return entry;
	}

	/**
	 * @param handle
	 *            of the entry to put amongst concepts
	 * @param entry
	 *            to put amongst concepts
	 */
	public synchronized void putConcept(String indexCode, Concept entry) {
		if (indexCode == null) return;
		indexCode = SkosUtil.normalizeAbout(indexCode);
		if (indexCode.length()==0) return;
		// DON'T: entry.setCode(handle);
		this.conceptsMap.put(indexCode, entry);
		if (entry.getInScheme() == null)
  		    primary.putConcept(entry);
	}

	/**
	 * @param handle
	 *            of the entry to put amongst concepts
	 * @param entry
	 *            to put amongst concepts
	 */
	public synchronized void removeConcept(String indexCode, Concept entry) {
		if (indexCode == null) return;
		indexCode = SkosUtil.normalizeAbout(indexCode);
		if (indexCode.length()==0) return;
		// DON'T: entry.setCode(handle);
		if (entry == this.conceptsMap.get(indexCode))
			this.conceptsMap.remove(indexCode);
	}

	/**
	 * @param origin:
	 *            handle of the entry to redirect
	 * @param handle
	 *            of the entry to which the origin is redirected
	 */
	public void putAlias(String origin, String handle) {
		if (origin == null) return;
		if (handle == null) return;
		origin = SkosUtil.normalizeAbout(origin);
		if (origin.length()==0) return;
		handle = SkosUtil.normalizeAbout(handle);
		if (handle.length()==0) return;
		this.redirections.put(origin, handle);
	}

	/**
	 * @param conceptMap
	 *            the concepts to set
	 */
	public synchronized void setConcept(List<Concept> someEntries) {
		for (Concept anEntry : someEntries) {
			Concept newEntry = anEntry;
			if (anEntry.getInScheme() == null)
				newEntry = this.primary.putConcept(anEntry);
			conceptsMap.put(newEntry.getNotation(this.getAbout()), newEntry);
		}
	}

	/**
	 * @param conceptMap
	 *            the concepts to set
	 */
	public synchronized void setConceptMap(
			Map<String, Concept> someEntries) {
		this.conceptsMap = someEntries;
	}

	/**
	 * @param code
	 *            the code to set
	 */
	public void setPrimary(ConceptScheme aPrimaryList) {
		if (aPrimaryList == null) return;
		this.primary = aPrimaryList;
		aPrimaryList.putNotationScheme(this);
	}
	/**
	 * @param redirections the redirections to set
	 */
	public void setAlias(Map<String, String> redirections) {
		this.redirections = redirections;
	}

	/**
	 * @param knownIndexes
	 *            the knownIndexes to set
	 */
	public void setUsage(SchemeUsageList knownIndexes) {
		for (SchemeUsage applicationEntry : knownIndexes) {
			this.putSchemeUsage(applicationEntry);
		}
	}
	
	public synchronized boolean putSchemeUsage(SchemeUsage aRoleUsage) {
		boolean result = primary.putSchemeUsage(aRoleUsage);
		aRoleUsage = primary.getUsage().find(aRoleUsage.getApplication(), aRoleUsage.getRole());
		aRoleUsage.setInScheme(this);
		return result;
	}

	public ArrayList<SearchResult> labelMatch(String prefix, String userLanguage, boolean strictLanguage, LabelMatchType matchType) {
		return SearchUtil.labelMatch( this.getManagerInstance(), prefix, userLanguage, strictLanguage, matchType, this.getConceptMap().values());
	}

	public ArrayList<SearchResult> labelMatch(String prefix, String userLanguage, boolean strictLanguage, LabelMatchType matchType, Collection<Concept> filter) {
		return SearchUtil.labelMatch( this.getManagerInstance(), prefix, userLanguage, strictLanguage, matchType, filter);
	}

	/*
     * How many concepts in this scheme?
     */
	@XmlTransient
	public int getSize() {
		return conceptsMap.size(); 
	}

	@XmlElement
	public LinkedList<String> getCreator() {
		return creators;
	}

	public void setCreator(LinkedList<String> creators) {
		this.creators = creators;
	}

	@XmlElement
	public LinkedList<String> getContributor() {
		return contributors;
	}

	public void setContributor(LinkedList<String> contributors) {
		this.contributors = contributors;
	}

	/**
	 * @return the supraLanguage
	 */
	@XmlElement
	public LinkedList<String> getSupraLanguage() {
		if (this.supraLanguage == null) this.supraLanguage = new LinkedList<String>();
		return this.supraLanguage;
	}


	public boolean equals(Object o) {
		return super.equals(o);
	}

	public boolean isApplicationUsageModified(String application) {
		return primary.isApplicationUsageModified(application);
	}

	public void setApplicationUsageModified(String application, boolean modified) {
		primary.setApplicationUsageModified(application, modified);
		
	}

}
